/**
 * 1、直接定义一个promise： 通过构造函数定义：
 * 注意： 只是new了一个对象，并没有调用它，我们传进去的函数就已经执行了
 * 所以： 通常会将Promise包在一个函数中
 */
var p = new Promise(function (resolve, reject) {
  //做一些异步操作
  setTimeout(function () {
    console.log('执行完成');
    resolve('随便什么数据');
  }, 2000);
});
function runAsync() {
  var p = new Promise(function (resolve, reject) {
    //做一些异步操作
    setTimeout(function () {
      console.log('执行完成');
      resolve('随便什么数据');
    }, 2000);
  });
  return p;
}

/**
 * 2、随便定义三个异步函数:asyncFn1, asyncFn2, asyncFn3 返回的均为promise对象，此时可进行
 *  在执行resolve的回调（也就是上面then中的第一个参数）时，如果抛出异常了（代码出错了），那么并不会报错卡死js，而是会进到这个catch方法中
 */

function asyncFn1() {
  var p = new Promise(function (resolve, reject) {
    //做一些异步操作
    setTimeout(function () {
      console.log('异步任务1执行完成');
      resolve('随便什么数据1');
    }, 1000);
  });
  return p;
}
function asyncFn2() {
  var p = new Promise(function (resolve, reject) {
    //做一些异步操作
    setTimeout(function () {
      console.log('异步任务2执行完成');
      resolve('随便什么数据2');
    }, 2000);
  });
  return p;
}
function asyncFn3() {
  var p = new Promise(function (resolve, reject) {
    //做一些异步操作
    setTimeout(function () {
      console.log('异步任务3执行完成');
      resolve('随便什么数据3');
    }, 3000);
  });
  return p;
}

// 调用 
asyncFn1()
  .then(function (data) {
    console.log(data);
    return asyncFn2();
  })
  .then(function (data) {
    console.log(data);
    return '直接返回数据';  //这里直接返回数据
  })
  .then(function (data) {
    console.log(data);
  })
  .catch(function (reason) {
    console.log('rejected');
    console.log(reason);
  });


/**
 * all的用法： all接收一个数组参数，里面的值最终都算返回Promise对象。这样，三个异步操作的并行执行的，等到它们都执行完后，all会把所有异步操作的结果放进一个数组中传给then
 * all方法的效果实际上是「谁跑的慢，以谁为准执行回调」
 */
Promise.all([asyncFn1(), asyncFn2(), asyncFn3()])
  .then(function (results) {
    console.log(results);
  });

/**
 * race的用法： runAsync1执行时间为1秒，runAsync2和runAsync3分别是2秒和3秒，所以runAsync1最先执行完，然后then即开始执行，此时runAsync2和runAsync3还没有执行完
 * all方法的效果实际上是:「谁跑的快，以谁为准执行回调」
 * 使用场景： 用race给某个异步请求设置超时时间，并且在超时后执行相应的操作， 如： 图片5秒内没有返回就提示用户图片加载失败
 */
Promise.race([runAsync1(), runAsync2(), runAsync3()])
  .then(function (results) {
    console.log(results);
  });

// race使用场景
//请求某个图片资源
function requestImg() {
  var p = new Promise(function (resolve, reject) {
    var img = new Image();
    img.onload = function () {
      resolve(img);
    }
    img.src = 'xxxxxx';
  });
  return p;
}

//延时函数，用于给请求计时
function timeout() {
  var p = new Promise(function (resolve, reject) {
    setTimeout(function () {
      reject('图片请求超时');
    }, 5000);
  });
  return p;
}
// 5秒内图片没有返回就执行timeout
Promise.race([requestImg(), timeout()])
  .then(function (results) {
    console.log(results);
  })
  .catch(function (reason) {
    console.log(reason);
  }); 


/**
 * 手工实现一个promise函数功能
 * promise相当于一个状态机，有三种状态：pending，fulfilled， rejected，
 * pending状态:  new Promise((resolve, reject)=> {...})
 * fulfilled状态：调用resolve(成功)，会由pending => fulfilled
 * rejected状态：调用reject(失败)，会由pending => rejected
 * 
 * Promise的实例方法主要有：
 * then方法: 当resolve(成功)/reject(失败)的回调函数 new Promise((resolve, reject)=> {...}).then(onFulfilled, onRejected); resolve(成功) onFulfilled会被调用, reject(失败) onRejected会被调用
 * catch方法：可以捕获前面then中发送的异常，包括resolve和reject， 注：onRrejected仅能捕获reject(失败)的异常，不能捕获resolve(成功)中的
 * 
 * Promise的静态方法
 * Promise.resolve：　返回一个fulfilled状态的promise对象　Promise.resolve('成功').then(function(value){...})
 * Promise.reject： 返回一个rejected状态的promise对象  Promise.reject('失败').then(function(value){...})
 * Promise.all： 接收一个promise对象数组为参数,只有全部为resolve才会进入到then，否则进入catch, Promise.all([p1, p2, p3]).then( (data)=>{...}).catch((error)=>{...})
 * Promise.race: 接收一个promise对象数组为参数, 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的就会进入then
 * 
 * 基于promise函数具有以上功能，按钮ES6原生Promise的方式来实现:
 * 1、实现一个构造函数， MyPromise
 * 
 * 
 */

//  1、MyPromise构造函数主体, 接收一个执行函数executorFn
function MyPromise(executorFn){
  let self = this;
  self.status = "pending"; // MyPromise当前的状态
  self.data = undefined  // MyPromise的值
  self.onResolvedCallback = []; // MyPromise执行resolve时的回调函数集，因为在Promise结束之前有可能有多个回调添加到它上面
  self.onRejectedCallback = []; // MyPromise执行reject时的回调函数集，因为在Promise结束之前有可能有多个回调添加到它上面
  
  // 在构造函数里定义并实现resolve和reject这两个函数
  function resolve(value){
    if(value instanceof MyPromise){
      value.then(resolve, reject);
      return
    }
    // 规范2.2.4规定：中要确保 onFulfilled 和 onRejected 方法异步执行，且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行
    setTimeout(()=>{  
      if(self.status === 'pending'){ // 调用resolve 回调对应onFulfilled函数   
        self.status = "fulfilled"; // 只能由pending状态 => fulfilled状态 (避免调用多次resolve reject)
        self.data = value;    
        self.onResolvedCallback.forEach( callBack => callBack(value));//执行resolve的回调函数，将value传递到callBack中
      }
    })
  }
   // reason失败态时接收的参数
  function reject(reason){
    setTimeout(()=>{
      if(self.status === 'pending'){// 调用reject 回调对应onRejected函数
        self.status = "rejected"; // 只能由pending状态 => rejected状态 (避免调用多次resolve reject)
        self.data = falue;       
        self.onRejectedCallback.forEach( callBack => callBack(reason));//执行reject的回调函数，将reason传递到callback中
      }
    })
  }

  //考虑到执行executorFn的过程中有可能出错，所以我们用try/catch块给包起来，并且在出错后以catch到的值reject掉这个Promise
  try{
    executorFn(resolve, reject);// 执行executorFn并传入相关参数
  }catch(err){
    reject(err)
  }
}

// 2、定义then方法，由于Promise/A+标准并没有要求返回的这个Promise是一个新的对象，但在Promise/A标准中，明确规定了then要返回一个新的对象，这里也让then返回一个新对象
// then方法接收两个参数，onResolved，onRejected，分别为MyPromise成功或失败后的回调, then默认参数就是把值往后传或者抛不同Promise的交互。
MyPromise.prototype.then = function(onResolve, onReject){
  let self = this;
  let myPromise2 ;
  // 根据标准，如果then的参数不是function，则我们需要忽略它，此处以如下方式处理
  if(typeof onResolve === "function"){
    onResolve = onResolve
  }else{
    onResolve = function(value){
      return value; // 通过return value 解决下面使用过程中问题2的参数穿透问题
    }
  }
  if(typeof onReject === "function"){
    onReject = onReject
  }else{
    onReject = function(value){
      return value; // 通过return value 解决下面使用过程中问题2的参数穿透问题
    }
  }

  // 外每个MyPromise对象都可以在其上多次调用then方法，而每次调用then返回的MyPromise的状态取决于
  // 那一次调用then时传入参数的返回值，所以then不能返回this，因为then每次返回的MyPromise的结果都有可能不同
  if(self.status === "pending"){
    // 如果当前的MyPromise还处于pending状态，我们并不能确定调用onResolved还是onRejected,只能等到MyPromise的状态确定后，才能确实如何处理
    // 所以我们需要把我们的 两种情况 的处理逻辑做为callback放入promise1(此处即this/self)的回调数组里, 处理逻辑与下面的if几乎相同
    return myPromise2 = new MyPromise(function(resolve, reject){
      // 当异步调用resolve时 将onFulfilled收集暂存到集合中
      self.onResolvedCallback.push(function(value) {
        try {
          let x = onResolved(value)
          myResolvePromise(myPromise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      });
      // 当异步调用rejected时 将onRejected收集暂存到集合中
      self.onRejectedCallback.push(function(reason) {
        try {
          let x = onRejected(self.data)
          myResolvePromise(myPromise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
  // then里面的FULFILLED/REJECTED状态时 为什么要加setTimeout 原因有2点：
  // 1、2.2.4规范 要确保 onFulfilled 和 onRejected 方法异步执行(且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行) 所以要在resolve里加上setTimeout
  // 2、2.2.6规范 对于一个promise，它的then方法可以调用多次.（当在其他程序中多次调用同一个promise的then时 由于之前状态已经为FULFILLED/REJECTED状态，则会走的下面逻辑),
  // 所以要确保为FULFILLED/REJECTED状态后 也要异步执行onFulfilled/onRejected
  if(self.status === "fulfilled"){
    // 如果myPromise1(此处即为this/self)的状态已经确定并且是fulfilled，我们调用onResolved因为考虑到有可能throw，所以我们将其包在try/catch块里
    return myPromise2 = new MyPromise(function(resolve, reject){
      setTimeout(()=>{
        try{
          let x = onResolve(self.data);
          myResolvePromise(myPromise2, x, resolve, reject) // 新的promise resolve 上一个onFulfilled的返回值
        }catch(err){
          reject(e) // 如果出错，以捕获到的错误做为myPromise2的结果
        }
      })
    })
  }
  if(self.status === "rejected"){
    return myPromise2 = new MyPromise(function(resolve, reject){
      setTimeout(()=>{
        try {
          let x = onRejected(self.data)
          myResolvePromise(myPromise2, x, resolve, reject) // 新的promise resolve 上一个onFulfilled的返回值
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

// 3、为了下文方便，我们顺便实现一个catch方法
MyPromise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

// 4、为解决各各promise相互调用的问题而添加的处理函数：即将onResolved/onRejected的返回值，x，当成一个可能是Promise的对象并以最保险的方式调用x上的then方法
// myResolvePromise作用：根据x的值来决定myPromise2的状态的函数：即myPromise2 = promise1.then(onResolved, onRejected)`里`onResolved/onRejected`的返回值
// `resolve`和`reject`实际上是`myPromise2`的`executorFn`的两个实参，因为很难挂在其它的地方，所以一并传进来。
/**
 * 对resolve 进行改造增强 针对resolve中不同值情况 进行处理
 * @param  {promise} myPromise2 promise1.then方法返回的新的promise对象
 * @param  {[type]} x         promise1中onFulfilled的返回值
 * @param  {[type]} resolve   promise2的resolve方法
 * @param  {[type]} reject    promise2的reject方法
 */
function myResolvePromise(myPromise2, X, resolve, reject){
  let then, thenCalledOrThrow = false;
  // 条件promise === x ，相当于promise.then之后return了自己，因为then会等待return后的promise，导致自己等待自己，一直处于等待，如下面这段执行代码会触发：x === myPromise2
  // 如：let p1then = promise1.then((value)=>{ return p1then; }, (err)=>{ return p1then; }); 前端return自己，后面再次调用then时 p1then.then(value=>{...}, err=>{...})
  if(x === myPromise2){//如果 promise 和 x 指向同一对象，以 TypeError 为据因拒绝执行 promise
    reject(new TypeError('Chaining cycle detected for promise!'));// 抛出循环引用的错误 即可
    return
  }

  // 对应标准2.3.2节： x 为 promise ，接受其状态
  if(x instanceof MyPromise){
    // 如果 x 处于等待态， promise 需保持为等待态直至 x 被执行或拒绝
    if(x.status === "pending"){
      x.then(value => {
        myResolvePromise(myPromise2, vlaue, resolve, reject)
      }, err =>{
        reject(err)
      })
    }else{
      // 如果这个Promise的状态已经确定了，那么它肯定有一个“正常”的值，而不是一个thenable，所以这里直接取它的状态
      x.then(resolve, reject)
    }
    return;
  }

  // 对应标准2.3.3节：如果：x 为对象或者函数
  if( (x !== null) && ((typeof x === "function") || (typeof x === "object"))){
    try{ // x有可能不是thenable对象（即具有then方法的对象/函数），所以用try包一下。
      then = x.then; //设置 then 方法为 x.then; 2.3.3.1 因为x.then有可能是一个getter，这种情况下多次读取就有可能产生问题
      // 如果 then 是函数，将 x 作为函数的作用域 this 调用之,其第一个参数为 resolvePromise ，第二个参数为 rejectPromise:
      if(typeof then === "function"){
        then.call(x, value =>{ // 这三处谁先执行就以谁的结果为准
          if(thenCalledOrThrow) return;
          thenCalledOrThrow = true;
          myResolvePromise(myPromise2, vlaue, resolve, reject);
          return;
        }, err => { // 这三处谁先执行就以谁的结果为准
          if(thenCalledOrThrow) return;
          thenCalledOrThrow = true;
          reject(err)
          return;
        })
      }else{
        // 如果 then 不是函数，以 x 为参数执行 promise
        resolve(x);
      }
    }catch(e){ // 这三处谁先执行就以谁的结果为准
      if(thenCalledOrThrow) return;
      thenCalledOrThrow = true;
      reject(err)
      return;
    }
  }else{
    // 如果 x 不为对象或者函数，以 x 为参数执行 promise
    resolve(x);
  }
}

// MyPromise上的一些静态方法：
// 返回一个fulfilled状态的MyPromise对象
MyPromise.resolve = function(value){
  return new MyPromise(function(resolve, reject){
    resolve(value);
  })
}
// 返回一个rejected状态的MyPromise对象
MyPromise.reject = function(value){
  return new MyPromise(function(resolve, reject){
    reject(value);
  })
}
/**
 * Promise.all Promise进行并行处理
 * 参数: promise对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 当这个数组里的所有promise对象全部进入FulFilled状态的时候，才会resolve。
 */
MyPromise.all = function(myPromiseArr){
  return new MyPromise((resolve, reject) => {
    let values = [];
    let count = 0;
    myPromiseArr.forEach((myPromise, index) => {
      myPromise.then(value => {
        values[index] = vlaue;
        count++;
        if(count === myPromiseArr.length){
          resolve(values)
        }
      }, reject)
    })
  })
}

/**
 * Promise.race
 * 参数: 接收 promise对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话，就会继续进行后面的处理(取决于哪一个更快)
 */
MyPromise.race = function (myPromiseArr) {
  return new MyPromise((resolve, reject) => {
    myPromiseArr.forEach((myPromise) => {
      myPromise.then(resolve, reject);
      });
  });
}


/**
 * 通过上面1，2，3三步已经基本实现了Promise标准中所涉及到的内容， 但还面临下面几个问题：
 * 1、不同的Promise实现之间需要无缝的可交互，即jquery的Promise，ES6的Promise，和我们实现的Promise之间以及其它的Promise实现，应该并且是有必要无缝相互调用的(通过添中间处理函数来解决各promise相互调用的问题： myResolvePromise)
 * 2、穿透问题
 */

// 问题1、用MyPromise来代表我们实现的Promise，MyPromise中只判断了onResolved/onRejected的返回值是否为我们实现的Promise的实例，并没有做任何其它的判断
new MyPromise(function(resolve, reject) { // 我们实现的Promise
  setTimeout(function() {
    resolve(42)
  }, 2000)
}).then(function() {
  return new Promise.reject(2) // ES6的Promise
}).then(function() {
  return Q.all([ // jquery的Promise
    new MyPromise(resolve=>resolve(8)), // 我们实现的Promise
    new Promise.resolve(9), // ES6的Promise
    Q.resolve(9) // jquery的Promise
  ])
})
// 问题2、通过MyPromise的实现最后会alert出undefined，即8这个值会穿透两个then(说Promise更为准确)到达最后一个then里的foo函数里，成为它的实参，最终将会alert出8
new MyPromise(resolve=>resolve(8))
  .then()
  .then()
  .then(function foo(value) {
    alert(value)// 正确结果是 alert(8)
  })


